﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SourFruit.player;
using SourFruit.level;
using SourFruit.level.obj;
using Microsoft.Xna.Framework;
using System.Diagnostics;

namespace SourFruit
{
    public class Physics
    {
        private Level level;
        private Player player;
        private const float GRAVITY = 20f;
        private Rectangle PreviousPlayerRectangle = new Rectangle();        

        public Physics(Level _level, Player _player)
        {
            this.level = _level;
            this.player = _player;
        }

        public void MovePlayerX(Boolean positive, GameTime gametime)
        {
            if (positive)
            {
                player.Velocity.X = player.WALKING_SPEED;
                mDirection.X = MOVE_RIGHT;
            }
            else
            {
                player.Velocity.X = player.WALKING_SPEED;
                mDirection.X = MOVE_LEFT;
            }
            UpdatePositionX(gametime);
            if (CollisionGoal(false, positive))
            {
                level.Goal.CreateEventGoalReached();
                return;
            }

            if (CollisionPlatform(false, positive) || player.Position.X > 800 || player.Position.X < 0)
            {
                player.Position.X = PreviousPlayerRectangle.X;
                player.Position.Y = PreviousPlayerRectangle.Y;
                player.Velocity.X = 0f;
                player.CreateEventPositionChanged();          
                mDirection.Y = MOVE_DOWN;
                return;
            }
            CollisionCollectable(false, positive);
            if (player.Position.X > GameConfig.Instance.screenWidth/3)
            {
                level.MoveAllObjects((int)(player.Position.X - PreviousPlayerRectangle.X));
                player.Position.X = PreviousPlayerRectangle.X;
            }
            PreviousPlayerRectangle.X = player.BoundingBox.X;
            player.CreateEventPositionChanged();
        }

        public void Fall(GameTime gametime)
        {
            if (jumping)
                return;
            player.Velocity.Y += GRAVITY;
            mDirection.Y = MOVE_DOWN;
            UpdatePositionY(gametime);
            if (CollisionGoal(true, true))
            {
                level.Goal.CreateEventGoalReached();
                return;
            }
            if (CollisionPlatform(true, true))
            {
                player.Position.Y = PreviousPlayerRectangle.Y;
                player.CreateEventPositionChanged();                     
                player.Velocity.Y = 0;
                falling = false;                
                return;
            }
            CollisionCollectable(true, true);            
            player.CreateEventPositionChanged();
            falling = true;
            PreviousPlayerRectangle.Y = player.BoundingBox.Y;
        }

        public void Jump()
        {
            if (!jumping && !falling)
            {
                jumping = true;
                falling = false;
                mDirection.Y = MOVE_UP;
                player.Velocity.Y = player.JUMPING_SPEED;
            }
        }
 
        public void UpdateJump(GameTime gametime)
        {
            if (!jumping)
                return;
            player.Velocity.Y -= GRAVITY;
            mDirection.Y = MOVE_UP;
            UpdatePositionY(gametime);
            if (CollisionGoal(true, false))
            {
                level.Goal.CreateEventGoalReached();
                return;
            }
            if (CollisionPlatform(true, false))
            {
                player.Position.Y = PreviousPlayerRectangle.Y;
                jumping = false;
                falling = true;
                mDirection.Y = MOVE_DOWN;                
                player.CreateEventPositionChanged();
                return;
            }
            CollisionCollectable(true, false);
            if (player.Velocity.Y <= 0)
            {
                //Then set it on
                player.Position.Y = PreviousPlayerRectangle.Y;
                jumping = false;
                falling = true;
                mDirection.Y = MOVE_DOWN;
                player.CreateEventPositionChanged();
            }
            PreviousPlayerRectangle.Y = player.BoundingBox.Y;
            player.CreateEventPositionChanged();       
        }


        private Boolean CollisionGoal(bool Y, bool positive)
        {
            Goal c = level.Goal;
            //culling, so we don't check objects that are too far from the player
            if (c.PosX > GameConfig.Instance.screenWidth + c.Width || c.PosX < -c.Width - 5 || c.PosX - c.Width - 5 > player.Position.X
                || c.PosX + c.Width + 5 < player.Position.X || c.PosY - c.Height - 5 > player.Position.Y
                || c.PosY + c.Height + 5 < player.Position.Y)
                return false;
            if (Y)
            {
                if (CCDY(c, positive, true))
                {                    
                    return true;
                }
            }
            else if (CCDX(c, positive, true))
            {                
                return true;
            }
            return false;
        }

        private void CollisionCollectable(Boolean Y, Boolean positive)
        {
            foreach (var collectable in level.Collectables)
            {
                Collectable c = collectable.Value;
                //culling, so we don't check objects that are too far from the player
                if (c.PosX > GameConfig.Instance.screenWidth + c.Width || c.PosX < -c.Width - 5  || c.PosX - c.Width - 5 > player.Position.X
                    || c.PosX + c.Width + 5 < player.Position.X || c.PosY - c.Height - 5 > player.Position.Y
                    || c.PosY + c.Height + 5 < player.Position.Y) 
                    continue;
                if (Y)
                {
                    if (CCDY(c, positive, true))
                    {
                        c.CreateEventCollected();
                        return;
                    }
                }
                else if (CCDX(c, positive, true))
                {
                    c.CreateEventCollected();
                    return;
                }
                
                //if (down)
                //{
                //    if (player.Position.Y < temp.PosY && player.BoundingBox.Intersects(temp.BoundingBox))
                //    {
                //        level.Collectables[i].PosX = -9999;
                //        level.Collectables[i].CreateEventPositionChanged();
                //        return;
                //    }
                //}
                //else if (player.BoundingBox.Intersects(temp.BoundingBox))
                //{
                //    level.Collectables[i].PosX = -9999;
                //    level.Collectables[i].CreateEventPositionChanged();
                //    return;
                //}
            }
        }

        private bool CollisionPlatform(Boolean Y, Boolean positive)
        {
            foreach (var p in level.Platforms)
            {
                Platform platform = p.Value;
                //culling, so we don't check objects that are too far from the player
                if (platform.PosX > GameConfig.Instance.screenWidth + platform.Width || platform.PosX < -platform.Width - 5 
                    || platform.PosX - platform.Width - 5 > player.Position.X
                    || platform.PosX + platform.Width + 5 < player.Position.X 
                    || platform.PosY - platform.Height - 5 > player.Position.Y
                    || platform.PosY + platform.Height + 5 < player.Position.Y) 
                    continue;
                if (Y)
                {
                    if (CCDY(platform, positive, false))
                    {
                        if (!platform.CanStandOn)
                            player.GotHit();
                        return true;
                    }
                }
                else if (CCDX(platform, positive, false))
                {
                    if (!platform.CanStandOn)
                        player.GotHit();
                    return true;
                }
            }
            return false;
        }

        private bool CCDY(LevelObject obj, Boolean positive, Boolean collectable)
        {
            int playerX = player.BoundingBox.X;
            int playerY;
            if (PreviousPlayerRectangle.Top < player.BoundingBox.Top)
            {
                playerY = player.BoundingBox.Top - PreviousPlayerRectangle.Top;
                return Between(createRectangleList(Enumerable.Range(player.BoundingBox.Top, playerY), playerX, false), obj.BoundingBox, false, collectable);
            }
            playerY = PreviousPlayerRectangle.Y - player.BoundingBox.Y;
            return Between(createRectangleList(Enumerable.Range(player.BoundingBox.Y, playerY), playerX, false), obj.BoundingBox, false, collectable);
        }

        private bool CCDX(LevelObject obj, Boolean positive, Boolean collectable)
        {
            int playerY = player.BoundingBox.Top;
            int playerX;
            if (PreviousPlayerRectangle.X < player.BoundingBox.X)
            {
                playerX = player.BoundingBox.X - PreviousPlayerRectangle.X;
                return Between(createRectangleList(Enumerable.Range(player.BoundingBox.X, playerX), playerY, true), obj.BoundingBox, true, collectable);
            }
            else playerX = PreviousPlayerRectangle.X - player.BoundingBox.X;
            return Between(createRectangleList(Enumerable.Range(player.BoundingBox.X, playerX), playerY, true), obj.BoundingBox, true, collectable);
        }

        private Boolean Between(List<Rectangle> values, Rectangle obj, Boolean X, Boolean collectable)
        {
            if (!X)
            {
                foreach (Rectangle rec in values)
                {
                    if (rec.Intersects(obj))
                    {
                        if (collectable) return true;
                        if (PreviousPlayerRectangle.Y < obj.Top)
                            PreviousPlayerRectangle.Y = obj.Top - player.Height;
                        else PreviousPlayerRectangle.Y = obj.Bottom;
                        return true;
                    }
                }
            }
            else
            {
                foreach (Rectangle rec in values)
                {
                    if (rec.Intersects(obj))
                    {
                        if (collectable) return true;
                        if (PreviousPlayerRectangle.X < obj.Left)
                            PreviousPlayerRectangle.X = obj.Left - player.Width;
                        else PreviousPlayerRectangle.X = obj.Right;
                        return true;
                    }
                }
            }
            return false;
        }

        private List<Rectangle> createRectangleList(IEnumerable<int> values, int constant, Boolean X)
        {
            List<Rectangle> a = new List<Rectangle>();
            foreach (int value in values)
            {
                if (!X) a.Add(new Rectangle(constant, value, player.Width, player.Height));
                else a.Add(new Rectangle(value, constant, player.Width, player.Height));
            }
            return a;
        }

        private Boolean Between(IEnumerable<int> values, int left, int right)
        {
            foreach (int value in values)
            {
                if (value > left - 1 && value < right + 1)
                    return true;
            }
            return false;
        }

        //Update the Sprite and change it's position based on the passed in speed, direction and elapsed time.
        public void UpdatePositionY(GameTime gametime)
        {
            player.Position.Y += (int)(mDirection.Y * player.Velocity.Y * (float)gametime.ElapsedGameTime.TotalSeconds);
        }

        /// <summary>
        /// Updates the x-position of the player object based on the speed, direction and elapsed time.
        /// </summary>
        /// <param name="gametime"></param>
        public void UpdatePositionX(GameTime gametime)
        {
            player.Position.X += (int)(mDirection.X * player.Velocity.X * (float)gametime.ElapsedGameTime.TotalSeconds);
        }

        private Boolean falling = true;
        private Boolean jumping = false;
        private Vector2 mDirection = Vector2.Zero;


        private const int MOVE_UP = -1;
        private const int MOVE_DOWN = 1;
        private const int MOVE_LEFT = -1;
        private const int MOVE_RIGHT = 1;
    }
}
